using System;
using System.Security.Cryptography;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace SecurityComp
{
    /// <summary>
    /// Summary description for ClientSecurity.
    /// </summary>
    public class ClientSecurity
    {
        protected SymmetricAlgorithm clientKey;
        protected RSACryptoServiceProvider serverKey;
        public ClientSecurity(RSACryptoServiceProvider crypt)
        {
            serverKey = crypt;
            //
            // TODO: Add constructor logic here
            //
        }
        public LoginInfo DecryptLoginData(byte[] loginData)
        {
            return (LoginInfo)this.DecryptAsymmetric(loginData);
        }
        protected object DecryptAsymmetric(byte[] dataToDecrypt)
        {
            // Create the memory stream where the decrypted data
            // will be stored.
            MemoryStream msDecrypted = new MemoryStream();

            // Determine the block size for decrypting.
            int keySize = serverKey.KeySize / 8;

            // Move through the data one block at a time.
            byte[] decryptedBlock, rawBlock;
            for (int i = 0; i < dataToDecrypt.Length; i += keySize)
            {
                if ((dataToDecrypt.Length - i) > keySize)
                {
                    rawBlock = new byte[keySize];
                }
                else
                {
                    rawBlock = new byte[dataToDecrypt.Length - i];
                }

                // Copy a block of data.
                Buffer.BlockCopy(dataToDecrypt, i, rawBlock, 0, rawBlock.Length);

                // Decrypt a block of data.
                decryptedBlock = serverKey.Decrypt(rawBlock, false);

                // Write the decrypted data to the in-memory stream.
                msDecrypted.Write(decryptedBlock, 0, decryptedBlock.Length);
            }

            msDecrypted.Position = 0;
            BinaryFormatter f = new BinaryFormatter();
            return f.Deserialize(msDecrypted);
        }
        public byte[] EncryptLoginData(string userName, string password, DateTime serverDate)
        {
            this.clientKey = (SymmetricAlgorithm)(new RijndaelManaged());
            this.clientKey.IV = new byte[this.clientKey.IV.Length];

            HashHelper hashUtil = new HashHelper();
            LoginInfo loginInfo = new LoginInfo(userName,
                hashUtil.CreatePasswordHash(password), clientKey.Key, serverDate);
            return this.EncryptAsymmetric(loginInfo);
        }
        protected byte[] EncryptAsymmetric(object objectToEncrypt)
        {
            // Create the memory streams.
            MemoryStream msRaw = new MemoryStream();
            MemoryStream msEncrypted = new MemoryStream();

            BinaryFormatter f = new BinaryFormatter();
            f.Serialize(msRaw, objectToEncrypt);

            byte[] bytes = msRaw.ToArray();

            // Determine the optimum block size for encryption.
            int blockSize = 0;
            if (serverKey.KeySize == 1024)
            {
                // High encryption capabilities are in place.
                blockSize = 16;
            }
            else
            {
                // High encryption capabilities are not in place.
                blockSize = 5;
            }

            // Move through the data one block at a time.
            byte[] rawBlock, encryptedBlock;
            for (int i = 0; i < bytes.Length; i += blockSize)
            {
                if ((bytes.Length - i) > blockSize)
                {
                    rawBlock = new byte[blockSize];
                }
                else
                {
                    rawBlock = new byte[bytes.Length - i];
                }

                // Copy a block of data.
                Buffer.BlockCopy(bytes, i, rawBlock, 0, rawBlock.Length);

                // Encrypt the block of data.
                encryptedBlock = serverKey.Encrypt(rawBlock, false);

                // Write the block of data.
                msEncrypted.Write(encryptedBlock, 0, encryptedBlock.Length);
            }

            return msEncrypted.ToArray();
        }
    }
    public sealed class HashHelper
    {
        private const int saltLength = 4;

        // Creates a salted password to save in the database.
        public byte[] CreateDBPassword(string password)
        {
            // Create the unsalted password hash.
            byte[] unsaltedPassword = CreatePasswordHash(password);

            // Generate a random salt value.
            byte[] saltValue = new byte[saltLength];
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
            rng.GetBytes(saltValue);

            // Create a salted hash.
            return CreateSaltedPassword(saltValue, unsaltedPassword);
        }

        // Creates a basic (unsalted) password hash.
        public byte[] CreatePasswordHash(string password)
        {
            System.Text.UTF8Encoding enc = new System.Text.UTF8Encoding();
            SHA1 sha1 = SHA1.Create();
            return sha1.ComputeHash(enc.GetBytes(password));
        }

        // Create a salted hash with a given salt value.
        private byte[] CreateSaltedPassword(byte[] saltValue, byte[] unsaltedPassword)
        {
            // Add the salt to the hash.
            byte[] rawSalted = new byte[unsaltedPassword.Length + saltValue.Length];
            unsaltedPassword.CopyTo(rawSalted, 0);
            saltValue.CopyTo(rawSalted, unsaltedPassword.Length);

            // Create the salted hash.
            SHA1 sha1 = SHA1.Create();
            byte[] saltedPassword = sha1.ComputeHash(rawSalted);

            // Add the salt value to the salted hash.
            byte[] dbPassword = new byte[saltedPassword.Length + saltValue.Length];
            saltedPassword.CopyTo(dbPassword, 0);
            saltValue.CopyTo(dbPassword, saltedPassword.Length);

            return dbPassword;
        }

        // Compare the hashed password against the stored password
        public bool ComparePasswords(byte[] storedPassword, byte[] unsaltedPassword)
        {
            if (storedPassword == null || unsaltedPassword == null || unsaltedPassword.Length != storedPassword.Length - saltLength)
                return false;

            // Retrieve the salt value.
            byte[] saltValue = new byte[saltLength];
            int saltOffset = storedPassword.Length - saltLength;
            for (int i = 0; i < saltLength; i++)
                saltValue[i] = storedPassword[saltOffset + i];

            // Convert the hashed password to a salted password.
            byte[] saltedPassword = CreateSaltedPassword(saltValue, unsaltedPassword);

            // Compare the two salted hashes (they should be the same).
            return CompareByteArray(storedPassword, saltedPassword);
        }

        // Compare the contents of two byte arrays.
        private bool CompareByteArray(byte[] array1, byte[] array2)
        {
            if (array1.Length != array2.Length)
                return false;

            for (int i = 0; i < array1.Length; i++)
            {
                if (array1[i] != array2[i])
                    return false;
            }
            return true;
        }
    }
    [Serializable()]
    public class LoginInfo
    {
        public string UserName;
        public byte[] PasswordHash;
        public DateTime CreatedTime;
        public byte[] ClientKey;

        public LoginInfo()
        {
        }

        public LoginInfo(string userName, byte[] passwordHash, byte[] clientKey, DateTime serverDate)
        {
            this.UserName = userName;
            this.PasswordHash = passwordHash;
            this.CreatedTime = serverDate;
            this.ClientKey = clientKey;
        }
    }
    public class TicketInfo
    {
        public int UserID;
        public DateTime CreatedDate;
        public string IPAddress;
        public byte[] ClientKey;

        public TicketInfo(int userID, string IPAddress, byte[] clientKey)
        {
            this.UserID = userID;
            this.CreatedDate = DateTime.Now;
            this.IPAddress = IPAddress;
            this.ClientKey = clientKey;
        }
    }
}
